/**
 * Copyright (c) 2023 murenchao
 * taomu is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *       http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */
package cool.taomu.utils.lmdb

import cool.taomu.utils.crypto.Base64
import java.io.File
import java.io.Serializable
import java.nio.ByteBuffer
import org.apache.commons.lang3.SerializationUtils
import org.apache.commons.lang3.StringUtils
import org.lmdbjava.Cursor
import org.lmdbjava.Env
import org.lmdbjava.GetOp
import org.slf4j.LoggerFactory

import static java.nio.ByteBuffer.allocateDirect
import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL
import static org.lmdbjava.DbiFlags.MDB_CREATE

class LmdbUtils implements AutoCloseable {
	val static LOG = LoggerFactory.getLogger(LmdbUtils);
	String path = "./";
	int maxSize = 1024 * 1024;
	int maxDbs = 1;
	int maxReaders = 126;
	Env<ByteBuffer> env;

	new() {
		init();
	}

	new(String path, int maxSize, int maxDbs, int maxReaders) {
		this.path = path;
		this.maxSize = maxSize;
		this.maxDbs = maxDbs;
		this.maxReaders = maxReaders;
		init();
	}

	def init() {
		if (StringUtils.isNotBlank(path)) {
			var lmdbPath = new File(path);
			var flag = lmdbPath.exists;
			if (!flag) {
				flag = lmdbPath.mkdirs;
			}
			if (flag) {
				this.env = Env.create(PROXY_OPTIMAL).setMapSize(this.maxSize * 1024 * 1024).setMaxDbs(this.maxDbs).
					setMaxReaders(this.maxReaders).open(lmdbPath);
			}
		}
	}

	def put(String dbName, String key, Serializable value) {
		var db = env.openDbi(dbName, MDB_CREATE);
		try(var txn = env.txnWrite) {
			try(var Cursor<ByteBuffer> c = db.openCursor(txn)) {
				var serStr = new String(new Base64(SerializationUtils.serialize(value)).encode(), "UTF-8");
				c.put(bb(key), bb(serStr));
			}
			txn.commit;
		} finally {
			db.close;
		}
	}

	def remove(String dbName, String key) {
		var db = env.openDbi(dbName);
		try(var txn = env.txnWrite) {
			try(var c = db.openCursor(txn)) {
				c.put(bb(key), bb(""));
				c.delete();
			}
			txn.commit
		} finally {
			db.close;
		}
	}

	def static void main(String[] args) {
		try(var l = new LmdbUtils("./", 10, 20, 20)) {
			l.put("test", "hw", "Hello World")
			System.out.println(l.get("test", "hw"));
			l.remove("test", "hw");
			System.out.println(l.get("test", "hw"));
		}
	}

	def Serializable get(String dbName, String key) {
		var db = env.openDbi(dbName);
		var Serializable ser = null;
		try(var txn = env.txnRead) {
			var ByteBuffer buffer = null;
			try(var c = db.openCursor(txn)) {
				if (c.get(bb(key), GetOp.MDB_SET_KEY)) {
					buffer = c.^val();
				}
			}
			if (buffer !== null) {
				var int length = buffer.limit() + buffer.position();
				var byte[] bytes = newByteArrayOfSize(length);
				buffer.get(bytes);
				var serStr = SerializationUtils.deserialize(bytes) as String;
				var base64Byte = new Base64(serStr.bytes).decode;
				ser = SerializationUtils.deserialize(base64Byte);
			}
		} finally {
			db.close;
		}
		return ser;
	}

	def bb(Serializable value) {
		var ser = SerializationUtils.serialize(value);
		var ByteBuffer bb = allocateDirect(ser.length);
		bb.put(ser).flip();
		return bb;
	}

	override close() throws Exception {
		if (this.env !== null) {
			this.env.close
		}
	}

}
